package LogHelper

import (
	models "ShortLink/Data/Models"
	sysConst "ShortLink/Data/SysConst"
	"net"
	"net/http"
	"net/http/httputil"
	"os"
	"runtime/debug"
	"strings"
	"time"

	"encoding/json"

	"github.com/gin-gonic/gin"
	"github.com/natefinch/lumberjack"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

type Field = zapcore.Field

var Logger *zap.Logger

// InitLogger 初始化Logger
func Init() (err error) {
	cfg := new(models.LogConfig)

	fileName := getLogFileName()
	writeSyncer := getLogWriter(fileName, 1000, 1000, 1000)
	encoder := getEncoder()
	var l = new(zapcore.Level)
	err = l.UnmarshalText([]byte(cfg.Level))
	if err != nil {
		return
	}
	core := zapcore.NewCore(encoder, writeSyncer, l)

	Logger = zap.New(core, zap.AddCaller())
	zap.ReplaceGlobals(Logger) // 替换zap包中全局的logger实例，后续在其他包中只需使用zap.L()调用即可
	return
}

func getEncoder() zapcore.Encoder {
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	encoderConfig.TimeKey = "time"
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
	encoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder
	encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder
	return zapcore.NewJSONEncoder(encoderConfig)
}

func getLogWriter(filename string, maxSize, maxBackup, maxAge int) zapcore.WriteSyncer {
	lumberJackLogger := &lumberjack.Logger{
		Filename:   filename,
		MaxSize:    maxSize,
		MaxBackups: maxBackup,
		MaxAge:     maxAge,
	}
	return zapcore.AddSync(lumberJackLogger)
}

// GinRecovery recover掉项目可能出现的panic，并使用zap记录相关日志
func GinRecovery(stack bool) gin.HandlerFunc {
	return func(c *gin.Context) {
		defer func() {
			if err := recover(); err != nil {
				// Check for a broken connection, as it is not really a
				// condition that warrants a panic stack trace.
				var brokenPipe bool
				if ne, ok := err.(*net.OpError); ok {
					if se, ok := ne.Err.(*os.SyscallError); ok {
						if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
							brokenPipe = true
						}
					}
				}

				httpRequest, _ := httputil.DumpRequest(c.Request, false)
				if brokenPipe {
					Logger.Error(c.Request.URL.Path,
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
					)
					// If the connection is dead, we can't write a status to it.
					c.Error(err.(error)) // nolint: errcheck
					c.Abort()
					return
				}

				if stack {
					Logger.Error("[Recovery from panic]",
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
						zap.String("stack", string(debug.Stack())),
					)
				} else {
					Logger.Error("[Recovery from panic]",
						zap.Any("error", err),
						zap.String("request", string(httpRequest)),
					)
				}
				c.AbortWithStatus(http.StatusInternalServerError)
			}
		}()
		c.Next()
	}
}

func getLogFileName() string {
	today := time.Now().Format("2006-01-02")
	return today + ".log"
}

func Error(msg interface{}) {
	timestamp := time.Now().Format(sysConst.TimeFormat)
	Logger.Error(toString(msg), zap.String("timestamp", timestamp))
}

func Info(msg interface{}) {
	timestamp := time.Now().Format(sysConst.TimeFormat)
	Logger.Info(toString(msg), zap.String("timestamp", timestamp))
}

func Warn(msg interface{}) {
	timestamp := time.Now().Format(sysConst.TimeFormat)
	Logger.Warn(toString(msg), zap.String("timestamp", timestamp))
}

func Fatal(msg interface{}) {
	timestamp := time.Now().Format(sysConst.TimeFormat)
	Logger.Fatal(toString(msg), zap.String("timestamp", timestamp))
}

func toString(msg interface{}) string {
	jsonData, err := json.Marshal(msg)
	if err != nil {
		timestamp := time.Now().Format(sysConst.TimeFormat)
		Logger.Error("序列化失败"+err.Error(), zap.String("timestamp", timestamp))
		return ""
	} else {
		return string(jsonData)
	}
}

type LogInfo struct {
	Title string
	Msg   string
	Data  interface{}
}

type ErrorLog struct {
	Title  string
	Msg    string
	Data   interface{}
	Result interface{}
}
